﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Net.Http;
using System.Threading.Tasks;
using System.Threading;
using System.Net;
using System.Net.Http.Headers;
using RestNews.Data.Models;
using RestNews.Util.Security;

namespace RestNews.Util.DigestAuthentication
{
    public class AuthenticationHandler : DelegatingHandler
    {
        /// <summary>
        /// The database context
        /// </summary>
        private RestNewsContext db;

        /// <summary>
        /// Initializes a new instance of the <see cref="AuthenticationHandler"/> class.
        /// </summary>
        public AuthenticationHandler()
        {
            db = new RestNewsContext();
        }

        /// <summary>
        /// Sends an HTTP request to the inner handler to send to the server as an asynchronous operation.
        /// </summary>
        /// <param name="request">The HTTP request message to send to the server.</param>
        /// <param name="cancellationToken">A cancellation token to cancel operation.</param>
        /// <returns>
        /// Returns <see cref="T:System.Threading.Tasks.Task`1" />. The task object representing the asynchronous operation.
        /// </returns>
        protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
        {
            try
            {
                var headers = request.Headers;
                if (headers.Authorization != null)
                {
                    DigestHeader header = new DigestHeader(request.Headers.Authorization.Parameter, request.Method.Method);

                    if (NonceProvider.IsValid(db, header.Nonce, header.NounceCounter))
                    {
                        User user = db.Users.FirstOrDefault(u => u.Username == header.UserName);

                        if (user != null && UserResponseIsValid(header, user.Password.Trim()))
                        {
                            Person person = user.People.FirstOrDefault();
                            IList<string> roles = user.Roles.Select(r => r.Name.Trim()).ToList();
                            Principal principal = new Principal(new UserIdentity(person.Id, person.FirstName, person.LastName, person.Address, user.Username, roles));
                            
                            //Set principal
                            Thread.CurrentPrincipal = principal;
                            if (HttpContext.Current != null)
                            {
                                HttpContext.Current.User = principal;
                            }
                        }
                    }
                }

                return base.SendAsync(request, cancellationToken).ContinueWith((task) =>
                {
                    var response = task.Result;

                    if (response.StatusCode == HttpStatusCode.Unauthorized)
                    {
                        response.Headers.WwwAuthenticate.Add(new AuthenticationHeaderValue("Digest", DigestHeader.GetUnauthorizedResponseHeader(db).ToString()));
                    }

                    return response;
                });
            }
            catch (Exception)
            {
                return Task<HttpResponseMessage>.Factory.StartNew(() =>
                {
                    var response = new HttpResponseMessage(HttpStatusCode.Unauthorized);
                    response.Headers.WwwAuthenticate.Add(new AuthenticationHeaderValue("Digest", DigestHeader.GetUnauthorizedResponseHeader(db).ToString()));

                    return response;
                });
            }
        }

        /// <summary>
        /// Recreates the digest hash.
        /// </summary>
        /// <param name="header">The header.</param>
        /// <returns></returns>
        private static bool UserResponseIsValid(DigestHeader header, string password)
        {
            string ha1 = String.Format("{0}:{1}:{2}",
                                header.UserName,
                                header.Realm,
                                password).ToMD5Hash();

            string ha2 = String.Format("{0}:{1}",
                                header.Method,
                                header.Uri).ToMD5Hash();

            string computedResponse = String
                          .Format("{0}:{1}:{2}:{3}:{4}:{5}",
                                ha1,
                                header.Nonce,
                                header.NounceCounter,
                                header.Cnonce,
                                "auth",
                                ha2).ToMD5Hash();
            return String.CompareOrdinal(header.Response, computedResponse) == 0;
        }
    }
}